import Set from "./Set";
import HashMap from "../map/HashMap";
import Obj from "../core/Obj";
import FullCapacityException from "../exceptions/FullCapacityException";
import IllegalArgumentException from "../exceptions/IllegalArgumentException";
import Collections from "../utils/Collections";

const className = "hashSet";

/**
 * 由一个{@link HashMap}实例支持的不可变集合。它不保证集合的迭代顺序。
 *
 * 这个类为基本操作（添加、删除、包含和大小）提供恒定的时间性能，假设散列函数在桶中正确地分散元素。
 *
 * 每个HashSet实例都有一个容量。容量是用于存储集合中元素的数组的大小。它始终不小于集合大小。当元素添加到HashSet时，它的大小会自动增长但容量不会增
 * 长，但您可以手动增加它。
 *
 * @param E 存储在此集合中的元素类型
 * @see Set
 * @version 1.0
 */
export default class HashSet<E> extends Obj implements Set<E> {

  /**
   * HashMap实例。
   *
   * @private
   * @version 1.0
   */
  private readonly map: HashMap<E, Obj>;

  /**
   * 位于HashMap上value处的默认实例。
   *
   * 这些实例不会在任何地方被引用。
   *
   * @private
   * @version 1.0
   */
  private static readonly PRESENT: Obj = {
    equals(): boolean {
      return false;
    },
    hashCode(): number {
      return 0;
    },
    [Symbol.hasInstance]() {
      return false;
    }
  };

  /**
   * 构造一个具有默认元素和容量的 HashSet。
   *
   * 如果您不提供默认元素，它将不包含任何内容。
   * 如果您不提供默认容量，它会将容量设置为无限。
   *
   * @param elements 需要默认置入此集合的元素
   * @param capacity 此集合的容量
   * @throws {FullCapacityException} 这个集合无法容纳这些元素
   * @throws {IllegalArgumentException} 这个容量数字不正确
   * @version 1.0
   */
  public constructor(elements: E[] = [], capacity: number = -1) {
    super();

    if (capacity !== -1 && elements.length > capacity) {
      throw new FullCapacityException(className, capacity);
    }

    if (capacity < -1) {
      throw new IllegalArgumentException("capacity should be bigger than -1");
    }

    this.map = new HashMap([], capacity);

    if (elements !== undefined) {
      for (let element of elements) {
        this.add(element);
      }
    }
  }

  [Symbol.iterator](): Iterator<E> {
    return this.iterator();
  }

  /**
   * 尝试将指定的元素加入到集合中。
   *
   * 因集合不允许重复元素加入集合导致的失败，则返回false。
   *
   * 若集合因该元素的加入发生了变化，则返回true。
   *
   * @param e 需要加入到集合中的元素
   * @return {boolean} 若集合因该元素的加入发生了变化，则返回true，否则返回false
   * @throws {FullCapacityException} 集合已经满了
   * @version 1.0
   */
  public add(e: E): boolean {
    if (!this.isFull()) {
      return this.map.put(e, HashSet.PRESENT) === undefined;
    } else {
      throw new FullCapacityException(className, this.map.capacity());
    }
  }

  /**
   * 返回此集合的容量。
   *
   * @return {number} 返回此集合的容量
   * @version 1.0
   */
  capacity(): number {
    return this.map.capacity();
  }

  /**
   * 清空此集合。
   *
   * @version 1.0
   */
  public clear(): void {
    this.map.clear();
  }

  /**
   * 返回此集合是否包含指定的元素。
   *
   * @param e 需要查找的元素
   * @return {boolean} 如果此集合包含该元素，则返回true，否则返回false
   * @version 1.0
   */
  public contains(e: E): boolean {
    return this.map.containsKey(e);
  }

  public equals(b: Obj): boolean {
    if (b instanceof HashSet) {
      return Collections.equals(this, b);
    }
    return false;
  }

  /**
   * 增加此集合的容量使其可以容纳更多的元素。
   *
   * 给出的容量必须比现有的容量更大，否则将会抛出{@link IllegalArgumentException}。当容量被设定为-1时，表示此集合容量无穷大，之后将不能再增
   * 加容量，也将不再判断容量是否充满。
   *
   * @param capacity 新的容量
   * @throws {IllegalArgumentException} 新的容量比当前容量小或容量已无法增长
   */
  public grow(capacity: number) {
    if (capacity < this.map.capacity() && capacity !== -1) {
      throw new IllegalArgumentException(
        "capacity " + capacity + " is smaller than current capacity " + this.map.capacity() + " and is not -1");
    } else {
      this.map.grow(capacity);
      return true;
    }
  }

  public hashCode(): number {
    return this.map.hashCode();
  }

  /**
   * 返回此集合是否为空。
   *
   * @return {boolean} 如果此集合不包含任何元素，则返回true，否则返回false
   * @version 1.0
   */
  public isEmpty(): boolean {
    return this.map.isEmpty();
  }

  /**
   * 返回此集合是否已满。
   *
   * @return {boolean} 如果此集合存储的元素已到达容量，则返回true，否则返回false。若容量为-1，则永远不会满容量
   */
  public isFull(): boolean {
    return this.map.size() === this.map.capacity();
  }

  /**
   * 返回由集合内所有元素组成的迭代器。
   *
   * @return {Iterator<E>} 一个由集合内所有元素组成的迭代器
   * @see Iterator
   * @version 1.0
   */
  public iterator(): Iterator<E> {
    return this.map.keyArray()[Symbol.iterator]();
  }

  /**
   * 移除此集合中所有与指定元素相同的元素。
   *
   * 此方法会调用{@link Obj#equals}方法进行相等判断。
   *
   * @param e 与需要从集合中被删除的元素相等的元素
   * @return {boolean} 如果有元素从集合中被删除，则返回true，否则返回false
   * @version 1.0
   */
  public remove(e: E): boolean {
    return this.removeFirst(e);
  }

  /**
   * 使用过滤器，移除此集合中所有符合过滤器要求的元素。
   *
   * @param filter 一个lambda表达式，当表达式返回true时，表示该元素符合过滤器要求
   * @return {boolean} 如果有元素从集合中被删除，则返回true，否则返回false
   * @version 1.0
   */
  public removeIf(filter: (e: E) => boolean): boolean {
    let flag = false;
    this.map.forEach(
      key => {
        if (filter(key)) {
          this.remove(key);
          flag = true;
        }
      }
    );
    return flag;
  }

  /**
   * 移除此集合中第一个与指定元素相同的元素。
   *
   * 此方法会调用{@link Obj#equals}方法进行相等判断。
   *
   * @param e 与需要从集合中被删除的元素相等的元素
   * @return {boolean} 如果有元素从集合中被删除，则返回true，否则返回false
   * @version 1.0
   */
  public removeFirst(e: E): boolean {
    return this.map.remove(e) !== undefined;
  }

  /**
   * 返回此集合中的元素数。
   *
   * @return {number} 返回此集合中的元素数
   * @version 1.0
   */
  public size(): number {
    return this.map.size();
  }

  /**
   * 返回由集合内所有元素组成的数组。
   *
   * 该数组内的元素会是集合中元素的<strong>浅拷贝</strong>，因此后续对集合的操作不会影响到数组的浅层，反之亦然。
   *
   * @return {E[]} 一个由集合内所有元素组成的数组
   * @version 1.0
   */
  public toArray(): E[] {
    return this.map.keyArray();
  }

  public toString(): string {
    return this.toArray().toString();
  }
}
